package org.iabako.server.controller.timer.logtrack;

import org.apache.log4j.Logger;
import org.iabako.server.tools.ServerTools;
import org.iabako.shared.tools.GenericTools;
import org.springframework.stereotype.Controller;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;

/**
 * Created by jose on 18/05/15.
 */

@Controller
public class ApacheLogTrackController {

    static Logger log = Logger.getLogger(ApacheLogTrackController.class.getName());

    private final static String APACHE_OTHER_VHOST_ACCESS_LOG = "/var/log/apache2/other_vhosts_access.log";
    //Google ip addresses
    private final static String[] SKIP_IP_ADDRESSES_LIST = new String[]{"66.249.", "66.102."};

    public String trackApacheLogs() {
        log.info("Executing ApacheLogTrackController...");
        Map<String, List<String[]>> ipLogsMap = new LinkedHashMap<String, List<String[]>>();

        getLogLinesToAnalyze(ipLogsMap, APACHE_OTHER_VHOST_ACCESS_LOG);
        //we look for logs also in ".1" files because of log rotation
        getLogLinesToAnalyze(ipLogsMap, APACHE_OTHER_VHOST_ACCESS_LOG + ".1");

        return analyseApacheLogs(ipLogsMap);
    }

    private void getLogLinesToAnalyze(Map<String, List<String[]>> ipLogsMap, String apacheOtherVhostAccessLog) {
        try {
            BufferedReader br = new BufferedReader(new FileReader(apacheOtherVhostAccessLog));

            ServerTools serverTools = new ServerTools();
            Date yesterday = serverTools.addDaysToDate(serverTools.resetTime(new Date()), -1);

            for (String line; (line = br.readLine()) != null; ) {

                //escape space separator if it's inside of quotes
                String[] logLine = line.split("\\s(?=([^\"]*\"[^\"]*\")*[^\"]*$)");
                String sourceIPAddress = logLine[1];
                Date date = serverTools.resetTime(ServerTools.getDateFromApacheLogs(logLine[4].substring(1)));

                if (date.compareTo(yesterday) != 0) {
                    continue;
                }

                if (!skipLine(logLine)) {
                    List<String[]> logs;
                    if ((logs = ipLogsMap.get(sourceIPAddress)) == null) {
                        ipLogsMap.put(sourceIPAddress, new ArrayList<String[]>());
                        logs = ipLogsMap.get(sourceIPAddress);
                    }
                    logs.add(logLine);
                }
            }
        } catch (FileNotFoundException e) {
            log.warn("This file does not exist in system file : " + apacheOtherVhostAccessLog);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
    }

    private static String analyseApacheLogs(Map<String, List<String[]>> ipLogsMap) {
        ServerTools serverTools = new ServerTools();
        Date yesterday = serverTools.addDaysToDate(serverTools.resetTime(new Date()), -1);

        //First character of token 4 is "["
        StringBuilder result = new StringBuilder();
        Map<String, List<String>> useAgentMemory = new HashMap<String, List<String>>();
        Map<String, List<String>> osMemory = new HashMap<String, List<String>>();
        Map<String, List<String>> browserMemory = new HashMap<String, List<String>>();
        List<String> http400Errors = new ArrayList<String>();
        List<String> http500Errors = new ArrayList<String>();
        //OS Counts
        int countOSWindows = 0;
        int countOSMac = 0;
        int countOSIPhone = 0;
        int countOSAndroid = 0;
        int countOSOthers = 0;
        //Browser Counts
        int countBrowserChrome = 0;
        int countBrowserFirefox = 0;
        int countBrowserIE = 0;
        int countBrowserOthers = 0;

        for (String sourceIPAddress : ipLogsMap.keySet()) {

            List<String[]> logsByIPAddress = ipLogsMap.get(sourceIPAddress);

            for (String[] logLine : logsByIPAddress) {
                String httpResponseCode = logLine[7];
                String userAgent = logLine[10];
                Date date = ServerTools.getDateFromApacheLogs(logLine[4].substring(1));

                //catch 400 and 500 http responses
                if (httpResponseCode.startsWith("4") || httpResponseCode.startsWith("5")) {
                    String httpRequest = logLine[6];
                    if (httpResponseCode.startsWith("4")) {
                        http400Errors.add(httpResponseCode + " : " + sourceIPAddress + " : " + GenericTools.formatDateSql(date) + " : " + httpRequest);
                    } else {
                        http500Errors.add(httpResponseCode + " : " + sourceIPAddress + " : " + GenericTools.formatDateSql(date) + " : " + httpRequest);
                    }
                }

                //OS stats
                if (!useAgentMemory.containsKey(sourceIPAddress) || !useAgentMemory.get(sourceIPAddress).contains(userAgent)) {

                    String os = userAgent.contains("Windows") ? "Windows"
                            : userAgent.contains("Macintosh") ? "Macintosh"
                            : userAgent.contains("iPhone") ? "iPhone"
                            : userAgent.contains("Android") ? "Android"
                            : userAgent.startsWith("\"Mozilla") ? "Other" : "NA";

                    if (userAgent.contains("Windows")) {
                        countOSWindows++;
                    } else if (userAgent.contains("Macintosh")) {
                        countOSMac++;
                    } else if (userAgent.contains("iPhone")) {
                        countOSIPhone++;
                    } else if (userAgent.contains("Android")) {
                        countOSAndroid++;
                    } else if (userAgent.startsWith("\"Mozilla")) {
                        countOSOthers++;
                    }
                    addObjectToMap(useAgentMemory, sourceIPAddress, userAgent);
                    addObjectToMap(osMemory, sourceIPAddress, os);
                }

                String browser = userAgent.contains("Chrome") ? "Chrome"
                        : userAgent.contains("Firefox") ? "Firefox"
                        : userAgent.contains("Trident") || userAgent.contains("MSIE") ? "IE"
                        : userAgent.startsWith("\"Mozilla") ? "XX" : "NA";

                if (!browserMemory.containsKey(sourceIPAddress) || !browserMemory.get(sourceIPAddress).contains(browser)) {
                    //Browser stats
                    if ("Chrome".equals(browser)) {
                        countBrowserChrome++;
                    } else if ("Firefox".equals(browser)) {
                        countBrowserFirefox++;
                    } else if ("IE".equals(browser)) {
                        countBrowserIE++;
                    } else if ("XX".equals(browser)) {
                        countBrowserOthers++;
                    }
                    addObjectToMap(browserMemory, sourceIPAddress, browser);
                }
            }
        }

        boolean disableLocationInfo = false;
        Map<String, Integer> visitorsByCountries = new HashMap<String, Integer>();
        StringBuilder visitorDetails = new StringBuilder();
        int visitors = 0;
        for (String sourceIPAddress : ipLogsMap.keySet()) {

            if (osMemory.get(sourceIPAddress).size() == 1 && osMemory.get(sourceIPAddress).contains("NA")
                    && browserMemory.get(sourceIPAddress).size() == 1 && browserMemory.get(sourceIPAddress).contains("NA")) {
                //This is potentially a robot
                continue;
            }
            visitors++;

            List<String[]> logsByIPAddress = ipLogsMap.get(sourceIPAddress);
            String dateStart = GenericTools.formatDateSql(ServerTools.getDateFromApacheLogs(logsByIPAddress.get(0)[4].substring(1)));
            String dateEnd = GenericTools.formatDateSql(ServerTools.getDateFromApacheLogs(logsByIPAddress.get(logsByIPAddress.size() - 1)[4].substring(1)));

            String locationInfo = !disableLocationInfo ? getLocation(sourceIPAddress) : null;
            if (locationInfo == null) {
                disableLocationInfo = true;
            }
            visitorDetails.append(sourceIPAddress + " : " + dateStart + " : " + dateEnd + " : "
                    + getListAsString(osMemory.get(sourceIPAddress)) + " : "
                    + getListAsString(browserMemory.get(sourceIPAddress)) + " : " + locationInfo + "\n");

            if (!disableLocationInfo) {
                String country = locationInfo.substring(locationInfo.indexOf("(") + 1, locationInfo.indexOf(")"));
                Integer counter = visitorsByCountries.get(country);
                if (counter == null) {
                    visitorsByCountries.put(country, 1);
                } else {
                    visitorsByCountries.put(country, counter + 1);
                }
            }
        }

        result.append("Dear Admin,\nThis is the visitors tracking log of " + GenericTools.formatDate(yesterday) + " : \n\n");
        result.append("<b>Visitors by IP Address : </b>" + ipLogsMap.size() + "\n");
        result.append("<b>Visitors by IP Address (without potential robots) : </b>" + visitors + "\n\n");
        result.append("<b>Visitors by Country (without potential robots) : </b>\n");
        for (String country : visitorsByCountries.keySet()) {
            result.append(country + " : " + visitorsByCountries.get(country) + "\n");
        }

        result.append("\n<b>Visitors by Operating System : </b>\n");
        result.append("Windows : " + countOSWindows + "\n");
        result.append("Mac : " + countOSMac + "\n");
        result.append("Android : " + countOSAndroid + "\n");
        result.append("iPhone : " + countOSIPhone + "\n");
        result.append("Others : " + countOSOthers + "\n\n");

        result.append("<b>Visitors by Browser : </b>\n");
        result.append("Chrome : " + countBrowserChrome + "\n");
        result.append("Firefox : " + countBrowserFirefox + "\n");
        result.append("Internet Explorer : " + countBrowserIE + "\n");
        result.append("Others : " + countBrowserOthers + "\n\n");

        result.append("<b>Number of HTTP 400 Errors : </b>" + http400Errors.size() + "\n");
        result.append("<b>Number of HTTP 500 Errors : </b>" + http500Errors.size() + "\n");

        if (http500Errors.size() > 0) {
            result.append("\n<b>================== 500 HTTP ERRORS DETAILS ==================</b>\n\n");
            for (String error : http500Errors) {
                result.append(error + "\n");
            }
        }

        result.append("\n<b>=============== VISITORS DETAILS ==============</b>\n\n");
        result.append(visitorDetails);

        return result.toString();
    }

    private static String getListAsString(List<String> strings) {
        String result = "";
        for (String s : strings) {
            result += s + ",";
        }
        return result.substring(0, result.length() - 1);
    }

    private static String getLocation(String ipAddress) {
        try {
            URL url = new URL("http://freegeoip.net/csv/" + ipAddress);

            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.connect();
            InputStream is = connection.getInputStream();

            int status = connection.getResponseCode();
            if (status != 200) {
                return null;
            }

            BufferedReader reader = new BufferedReader(new InputStreamReader(is));

            for (String line; (line = reader.readLine()) != null; ) {
                String[] result = line.split(",");
                return result[4] + ", " + result[5] + " (" + result[2] + ")";
            }
        } catch (IOException e) {
            System.out.print(e);
        }
        return null;
    }

    private static void addObjectToMap(Map<String, List<String>> memory, String sourceIPAddress, String object) {
        List<String> objectList;
        if ((objectList = memory.get(sourceIPAddress)) == null) {
            memory.put(sourceIPAddress, new ArrayList<String>());
            objectList = memory.get(sourceIPAddress);
        }
        if (!objectList.contains(object)) {
            objectList.add(object);
        }
    }

    private static boolean skipLine(String[] logLine) {
        String sourceIPAddress = logLine[1];

        for (String blackListedIPAddress : SKIP_IP_ADDRESSES_LIST) {
            if (sourceIPAddress.startsWith(blackListedIPAddress)) {
                return true;
            }
        }
        return false;
    }

    /*public static void main(String[] args) {
        trackApacheLogs();
    }*/
}
